<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class WebSocket extends Swoole\Protocol\WebSocket
{

    private static $CI;
    private static $table;

    function __construct($config = array())
    {
        if(empty(self::$CI) && self::$CI = get_instance()){
            if (!is_cli()) {
                $data = array(
                    'heading' => '运行出错',
                    'message' => '<p>请使用 PHP-CLI 模式运行</p>',
                );
                self::$CI->load->view('errors/html/error_general', $data);
                exit(self::$CI->output->get_output());
            }
            $table = new swoole_table(1024);
            $table->column('fd', swoole_table::TYPE_INT); // 系统给客户端标记的自增 ID
            $table->column('client_id', swoole_table::TYPE_STRING, 20); // 对应的客户端 ID
            $table->column('time', swoole_table::TYPE_INT, 10); // 创建的时间，用来验证二维码是否超时
            $table->column('scanned', swoole_table::TYPE_STRING, 20); // 用来标记该二维码是否被成功扫描过（如果扫过的话会记录扫描者的 fd）
            $table->create();
            self::$table = $table;
        }
        parent::__construct($config);
    }

    /**
     * 在每个worker进程开启前回调
     *
     * @param swoole_websocket_server $server swoole_websocket_server 对象
     * @param int $worker_id worker进程ID
     */
    function onStart($server, $worker_id = 0)
    {
        Swoole::$php->router(array($this, 'router'));
        parent::onStart($server, $worker_id);
    }

    /**
     * 当WebSocket客户端与服务器建立连接并完成握手后会回调此方法
     *
     * @param int $fd 客户端连接的标识符，在Server程序中是唯一的
     * @return void
     */
    function onEnter($fd)
    {
        $this->log("WebSocket connection #{$fd} entered at " . date('Y-m-d H:i:s'));
    }

    /**
     * 当服务器收到来自客户端的数据帧时会回调此方法
     *
     * @param int $fd 客户端连接的标识符，在Server程序中是唯一的
     * @param array $ws 包含了客户端发来的数据帧信息，其中 message 为客户端 send 的内容
     * @return void
     */
    function onMessage($fd, $ws)
    {
        $data = json_decode($ws['message']);
        if (!empty($data->method)) {
            switch ($data->method) {
                case 'shake_hands':
                    // 握手（扫码页发起）
                    if (!empty($data->code)) {
                        $this->send($fd, json_encode(array('method' => $data->method, 'code' => encrypt(decrypt($data->code)), 'status' => 0)));
                        $this->log("WebSocket connection #{$fd} \"shake_hands\" success");
                    } else {
                        $this->send($fd, json_encode(array('status' => 21004))); // 握手 code 为空
                        $this->log("WebSocket connection #{$fd} error 21004 on \"shake_hands\"");
                    }
                    break;
                case 'register':
                    // 注册（扫码页发起）
                    if($client_id = decrypt($data->client_id)){
                        self::$table->set($client_id, array('fd' => $fd, 'client_id' => $client_id, 'time' => time(), 'scanned' => ''));
                        $this->send($fd, json_encode(array('method' => $data->method, 'status' => 0)));
                        $this->log("WebSocket connection #{$fd} \"register\" success");
                    }else{
                        $this->log("WebSocket connection #{$fd} error 20001 on \"register\"");
                    }
                    break;
                case 'scan':
                    // 扫描（手机页发起）
                    $client_id = decrypt($data->client_id);
                    if($table = self::$table->get($client_id)){
                        if(!empty($table['scanned']) && $table['scanned'] != $fd){
                            // 该页面已经被扫描过
                            $static = 20003;
                        }elseif($table['time'] < time() - config_item('qrcode_valid_time') * 60){
                            // 扫码页已过期
                            $static = 20002;
                        }else{
                            // 扫码页访问成功
                            self::$table->set($client_id, array('scanned' => $fd)); // 标记该二维码已被成功扫过
                            $this->send($table['fd'], json_encode(array('method' => $data->method, 'status' => 0))); // 向扫描页回执一条成功的消息
                            $static = 0;
                            $this->log("WebSocket connection #{$fd} \"scan\" success");
                        }
                    }else{
                        // 扫码页被关闭或地址无效
                        $static = 20001;
                    }
                    $this->send($fd, json_encode(array('method' => $data->method, 'status' => $static)));
                    if($static){
                        $this->log("WebSocket connection #{$fd} error {$static} on \"scan\"");
                    }
                    break;
                case 'login':
                    // 登录（手机页发起）
                    $client_id = decrypt($data->client_id);
                    if($table = self::$table->get($client_id)){
                        if (empty($table['scanned']) || $table['scanned'] == $fd) {
                            $static = 0;
                            $this->send($table['fd'], json_encode(array('method' => $data->method, 'nickname' => $data->nickname, 'status' => 0))); // 向扫码页回执一条成功的消息
                            $this->log("WebSocket connection #{$fd} \"login\" success");
                        } else {
                            // 扫码页被关闭或地址无效
                            $static = 20001;
                        }
                    }else{
                        // 扫码页被关闭或地址无效
                        $static = 20001;
                    }
                    $this->send($fd, json_encode(array('method' => $data->method, 'status' => $static)));
                    if($static){
                        $this->log("WebSocket connection #{$fd} error {$static} on \"login\"");
                    }
                    break;
                default:
                    $this->send($fd, json_encode(array('status' => 21003))); // 无效的请求方式
                    $this->log("WebSocket connection #{$fd} error 21003 on unknow method: ".$data->method);
            }
        } else {
            $this->send($fd, json_encode(array('status' => 21003))); // 无效的请求方式
            $this->log("WebSocket connection #{$fd} error 21003 on bad message");
            $this->log($data);
        }
        //$this->broadcast($client_id, $ws['message']);
    }

    /**
     * 当WebSocket客户端关闭时回调此方法
     *
     * @param int $fd 客户端连接的标识符，在Server程序中是唯一的
     * @return void
     */
    function onExit($fd)
    {
        $this->log("onOffline: " . $fd);
        foreach (self::$table as $table) {
            if ($table['fd'] == $fd) {
                // 该客户端是扫码客户端，清除
                self::$table->del($table['client_id']);
                break;
            }elseif($table['scanned'] == $fd){
                // 该客户端是扫描设备客户端，重置扫码状态
                self::$table->set($table['client_id'], array('scanned' => ''));
            }
        }
        //$this->broadcast($client_id, "onOffline: " . $client_id);
    }

    /**
     * 发送客户端广播
     *
     * @param int $client_id 客户端连接的标识符，在Server程序中是唯一的
     * @param string $msg 广播的内容
     * @return void
     */
    function broadcast($client_id, $msg)
    {
        foreach ($this->connections as $client => $info) {
            if ($client_id != $client) {
                $this->send($client, $msg);
            }
        }
    }

}
//require __DIR__'/phar://swoole.phar';
Swoole\Config::$debug = true;
Swoole\Error::$echo_html = false;